﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Windows.Markup;
using System.Xaml;
using System.Xaml.Schema;

using alib;
using alib.Array;
using alib.String;
using alib.Debugging;
using alib.Enumerable;
using alib.Reflection;

using agree.schema.xaml;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class xaml_obj_writer : XamlObjectWriter
	{
		public xaml_obj_writer(XamlLoadInstance xli)
			: base(xli.Schema.xsch, new _settings(xli))
		{
			this.xli = xli;
			this.sch = xli.Schema;
		}

		public readonly XamlLoadInstance xli;
		readonly schema.Schema sch;

		public sealed class _settings : XamlObjectWriterSettings
		{
			public _settings(XamlLoadInstance xli)
			{
				this.xli = xli;
				this.ExternalNameScope = xli.NameScope;
				this.RegisterNamesOnExternalNamescope = true;

				/// default values:

				//this.RootObjectInstance = null;
				//this.IgnoreCanConvert = false;
				//this.PreferUnconvertedDictionaryKeys = false;
				//this.SkipDuplicatePropertyCheck = false;
				//this.SkipProvideValueOnRoot = false;
			}
			readonly XamlLoadInstance xli;
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public void inject_ctor_arg()
		{
			var cf = CurrentFrame;
			if (cf.Instance == null && cf.XamlType is xaml_type)
			{
				var args = cf.PositionalCtorArgs;

				args = args == null ? new Object[] { this } : arr.Prepend(args, this);

				CurrentFrame.PositionalCtorArgs = args;
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		Type[] adjust_args(IIdentity prv, ref Object[] in_out)
		{
			var rgo = new Object[in_out == null ? 1 : in_out.Length + 1];

			var rgt = new Type[rgo.Length];
			for (int i = 0; i < rgt.Length; i++)
				rgt[i] = (rgo[i] = i == 0 ? prv : in_out[i - 1]).GetType();

			in_out = rgo;
			return rgt;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		ConstructorInfo find_ctor(Type t, IIdentity prv, ref Object[] rgo)
		{
			ConstructorInfo ctor;

			if ((ctor = t.GetConstructor(adjust_args(prv, ref rgo))) == null)
			{
				if (prv != rgo[0])
					throw new Exception();

				var rgP = (ctor = t.GetConstructors()[0]).GetParameters();
				var tp0 = rgP[0].ParameterType;

				if (!tp0.IsAssignableFrom(prv.GetType()))
					throw new Exception();

				switch (rgP.Length)
				{
					default:
						throw new Exception();
					case 1:
						rgo = new Object[] { prv };
						break;
					case 2:
						if (rgP[1].ParameterType == typeof(xaml_schema))
							rgo = new Object[] { prv, sch };
						else if (tp0 == typeof(IRtParent))
							rgo = new Object[] { prv, null };
						else
							throw new Exception();
						break;
					case 3:
						if (tp0 == typeof(IRtParent))
							rgo = new Object[] { prv, null, null };
						else
							throw new Exception();
						break;
				}
			}
			return ctor;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		IRtParent find_runtime_parent(IIdentity prv)
		{
			IRtParent rtp;
			do
				if ((rtp = prv as IRtParent) != null)
					return rtp;
			while ((prv = prv.Trace) != null);
			return null;
		}

		/// <summary> do not use: access result through xaml_load_instance </summary>
		public sealed override Object Result { get { throw not.valid; } }

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public ISchemaObj CreateInstance(schema.schema_type t, Object[] args)
		{
			IIdentity prv;

			if (sch._typeof<SysObj>().IsAssignableFrom(t))
				return SysObj.Instance;

			var par = ParentInstance;

			if (par is System.Collections.ArrayList)
				prv = SysObj.Instance;
			else if ((prv = (par ?? xli) as IIdentity) == null)
				throw new Exception("Trace not available");

			if (sch._typeof<IRtChild>().IsAssignableFrom(t) && (prv = find_runtime_parent(prv)) == null)
				throw new Exception("Runtime services not available");

			try
			{
				var obj = (ISchemaObj)find_ctor(t, prv, ref args).Invoke(args);

				t.KnownType.do_newobj(t, obj);		/// tmp tmp doesn't belong in xaml
				return obj;
			}
			catch (TargetInvocationException)
			{
				Debug.Print("passing TargetInvocationException to Xaml");
				throw;
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		protected override void OnAfterBeginInit(Object value)
		{
			IIdentity item;
			if ((item = value as IIdentity) != null && item.Trace == xli)
				xli.RootCreated(item);

#if need_xow_event_handling
			base.OnAfterBeginInit(value);
#endif
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		static MethodInfo _mi_get_CurrentProperty, _mi_get_ParentProperty, _mi_get_ParentInstance, _mi_get_ParentCollection, _mi_get_CurrentType;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		static MethodInfo _mi_get_ParentNameScope, _mi_get_CurrentFrame;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		static FieldInfo _fi_ctx, _fi_stack;
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		static Type Tframe;

		static xaml_obj_writer()
		{
			_fi_ctx = typeof(XamlObjectWriter).GetField("_context", (BindingFlags)0x26);
			var Tctx = _fi_ctx.FieldType;
			_mi_get_CurrentType = Tctx.GetMethod("get_CurrentType");
			_mi_get_CurrentProperty = Tctx.GetMethod("get_CurrentProperty");
			_mi_get_ParentProperty = Tctx.GetMethod("get_ParentProperty");
			_mi_get_ParentInstance = Tctx.GetMethod("get_ParentInstance");
			_mi_get_ParentCollection = Tctx.GetMethod("get_ParentCollection");
			_mi_get_ParentNameScope = Tctx.GetMethod("get_ParentNameScope");

			_fi_stack = Tctx.GetField("_stack", (BindingFlags)0x24);
			_mi_get_CurrentFrame = _fi_stack.FieldType.GetMethod("get_CurrentFrame");

			Tframe = _mi_get_CurrentFrame.ReturnType;
		}

		public sealed class XowFrame
		{
			[DebuggerBrowsable(DebuggerBrowsableState.Never)]
			static readonly MethodInfo mi_get_Instance, mi_get_PositionalCtorArgs, mi_set_PositionalCtorArgs, mi_get_InstanceRegisteredName;
			[DebuggerBrowsable(DebuggerBrowsableState.Never)]
			static readonly MethodInfo mi_get_XamlType, mi_get_Member, mi_get_Namespaces, mi_get_Depth, mi_get_IsObjectFromMember;
			[DebuggerBrowsable(DebuggerBrowsableState.Never)]
			static readonly MethodInfo mi_get_NameScopeDictionary, mi_get_Previous;

			static XowFrame()
			{
				mi_get_Instance = Tframe.GetMethod("get_Instance");
				mi_get_PositionalCtorArgs = Tframe.GetMethod("get_PositionalCtorArgs");
				mi_set_PositionalCtorArgs = Tframe.GetMethod("set_PositionalCtorArgs");
				mi_get_InstanceRegisteredName = Tframe.GetMethod("get_InstanceRegisteredName");
				mi_get_XamlType = Tframe.GetMethod("get_XamlType");
				mi_get_Member = Tframe.GetMethod("get_Member");
				mi_get_Namespaces = Tframe.GetMethod("get_Namespaces");
				mi_get_Depth = Tframe.GetMethod("get_Depth");
				mi_get_IsObjectFromMember = Tframe.GetMethod("get_IsObjectFromMember");
				mi_get_NameScopeDictionary = Tframe.GetMethod("get_NameScopeDictionary");
				mi_get_Previous = Tframe.GetMethod("get_Previous");
			}

			public XowFrame(Object _frame) { this._frame = _frame; }

			[DebuggerBrowsable(DebuggerBrowsableState.Never)]
			readonly Object _frame;

			public Object Instance { get { return mi_get_Instance.Invoke(_frame, null); } }

			public String InstanceRegisteredName { get { return (String)mi_get_InstanceRegisteredName.Invoke(_frame, null); } }

			public XamlType XamlType { get { return (XamlType)mi_get_XamlType.Invoke(_frame, null); } }

			public XamlMember XamlMember { get { return (XamlMember)mi_get_Member.Invoke(_frame, null); } }

			public Dictionary<String, String> Namespaces { get { return (Dictionary<String, String>)mi_get_Namespaces.Invoke(_frame, null); } }

			public bool IsObjectFromMember { get { return (bool)mi_get_IsObjectFromMember.Invoke(_frame, null); } }

			public INameScopeDictionary NameScopeDictionary { get { return (INameScopeDictionary)mi_get_NameScopeDictionary.Invoke(_frame, null); } }

			public int Depth { get { return (int)mi_get_Depth.Invoke(_frame, null); } }

			public Object[] PositionalCtorArgs
			{
				get { return (Object[])mi_get_PositionalCtorArgs.Invoke(_frame, null); }
				set { mi_set_PositionalCtorArgs.Invoke(_frame, new Object[] { value }); }
			}

			public XowFrame Previous
			{
				get
				{
					var prev = mi_get_Previous.Invoke(_frame, null);
					return prev == null ? null : new XowFrame(prev);
				}
			}

			public override String ToString() { return _frame == null ? "(null)" : _frame.ToString(); }
		};

		public Object ObjWriterContext { [DebuggerStepThrough] get { return _fi_ctx.GetValue(this); } }

		public Object Stack { get { return _fi_stack.GetValue(ObjWriterContext); } }

		public XowFrame CurrentFrame { get { return new XowFrame(_mi_get_CurrentFrame.Invoke(Stack, null)); } }

		public XamlType CurrentType { get { return (XamlType)_mi_get_CurrentType.Invoke(ObjWriterContext, null); } }

		public String CurrentPropertyName { get { var x = CurrentProperty; return x == null ? "(null)" : x.Name; } }

		public XamlMember CurrentProperty { get { return (XamlMember)_mi_get_CurrentProperty.Invoke(ObjWriterContext, null); } }

		public XamlMember ParentProperty { get { return (XamlMember)_mi_get_ParentProperty.Invoke(ObjWriterContext, null); } }

		public Object ParentInstance { get { return _mi_get_ParentInstance.Invoke(ObjWriterContext, null); } }

		public Object ParentCollection { get { return _mi_get_ParentCollection.Invoke(ObjWriterContext, null); } }

		public INameScopeDictionary ParentNameScope
		{
			[DebuggerStepThrough]
			get { return (INameScopeDictionary)_mi_get_ParentNameScope.Invoke(ObjWriterContext, null); }
		}

		static Type Tinsd;
		static FieldInfo fi_get_underlying_namescope;
		public INameScope ParentNameScopeTrue
		{
			[DebuggerStepThrough]
			get
			{
				var insd = ParentNameScope;
				if (insd == null)
					return null;
				if (Tinsd == null)
				{
					Tinsd = insd.GetType();
					fi_get_underlying_namescope = Tinsd.GetField("_underlyingNameScope", (BindingFlags)0x24);
				}
				return fi_get_underlying_namescope == null ? null : (INameScope)fi_get_underlying_namescope.GetValue(insd);
			}
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public IEnumerable<XowFrame> StackFrames
		{
			get
			{
				XowFrame cur = CurrentFrame;
				while (cur != null)
				{
					yield return cur;
					cur = cur.Previous;
				}
			}
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public IEnumerable<Object> StackInstances
		{
			get { return StackFrames.Select(xf => xf.Instance); }
		}

		[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
		public XowFrame[] _dbg_stack { get { return StackFrames.ToArray(); } }
	};
}
